package com.example.androidexample.qr;

import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;

import androidx.annotation.NonNull;

import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class CameraController {

    private static final String TAG = "CameraController";
    private Camera camera;
    private AutoFocus autoFocus;

    private int previewWidth = 0;
    private int previewHeight = 0;


    private static CameraController instance = new CameraController();
    private FrameDecodeFormatReader reader = new FrameDecodeFormatReader();
    private Handler handler;
    private Disposable disposable;
    private Camera.Parameters parameters;

    private CameraController() {
    }

    public static CameraController getInstance() {
        return instance;
    }

    public void openFacingBack(SurfaceHolder holder) {
        closeCamera();
        try {
            openCameraInternal(holder, getExpectedIndex(Camera.CameraInfo.CAMERA_FACING_BACK));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void openFacingFront(SurfaceHolder holder) {
        closeCamera();
        try {
            openCameraInternal(holder, getExpectedIndex(Camera.CameraInfo.CAMERA_FACING_FRONT));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    private Camera.PreviewCallback callback = new Camera.PreviewCallback() {

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {

            Log.d(TAG, "onPreviewFrame: data size = " + data.length);

            if (data != null) {
                disposable = Schedulers.io().scheduleDirect(() -> {
                    Log.d(TAG, "run: Thread = " + Thread.currentThread().getName());
                    DecodeResult result = decode(new NV21Frame(data, previewWidth, previewHeight, true));
                    Log.d(TAG, "run: result = " + result);
                    if (result.isStatus()) {
                        Log.d(TAG, "run: 结果是：" + result.getText());
                        disposable.dispose();
                        Message message = Message.obtain();
                        message.what = 200;
                        message.obj = result.getText();
                        handler.sendMessage(message);
                    } else {
                        if (!disposable.isDisposed()) {
                            camera.setOneShotPreviewCallback(callback);
                        }
                    }
                });
            } else {
                camera.setOneShotPreviewCallback(callback);
            }
        }
    };

    private DecodeResult decode(NV21Frame nv21Frame) {
        return decode(new PlanarYUVLuminanceSource(nv21Frame.getData(),nv21Frame.getWidthIN(), nv21Frame.getHeightIN(),
                0,0,nv21Frame.getWidthIN(),nv21Frame.getHeightIN(),false));
    }

    private DecodeResult decode(LuminanceSource source) {
        return  reader.decode(source);
    }

    public void startFrameDecode(Handler handler) {
        this.handler = handler;
        requestPreviewFrame();
    }

    public void stopFrameDecode() {
        disposable.dispose();
    }

    private void requestPreviewFrame() {
        Log.d(TAG, "requestPreviewFrame: ");
        camera.setOneShotPreviewCallback(callback);
    }

    private int getExpectedIndex(int expected) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        int num = Camera.getNumberOfCameras();
        for (int i = 0; i < num; i++) {
            Camera.getCameraInfo(i, info);
            if (info.facing == expected) {
                return i;
            }
        }
        return -1;
    }

    private int openCameraInternal(SurfaceHolder holder, int index) throws IOException {
        Log.d(TAG, "openCameraInternal: ");
        camera = Camera.open(index);
        if (camera == null) {
            return -2;   //CameraEvent.OpenError
        }

        Log.d(TAG, "openCameraInternal: 1");
        camera.setPreviewDisplay(holder);
        initCamera();
        startPreview();

        return 0;   //CameraEvent.OpenSuccessfully
    }



    private void initCamera() {
        Log.d(TAG, "initCamera: ");
        String flatten = camera.getParameters().flatten();
        parameters = camera.getParameters();

        //预览大小
        try {

            findBsetPreviewSize(parameters.getSupportedPreviewSizes());
            parameters.setPreviewSize(parameters.getSupportedPreviewSizes().get(0).width,
                    parameters.getSupportedPreviewSizes().get(0).height);

            previewWidth = parameters.getSupportedPreviewSizes().get(0).width;
            previewHeight = parameters.getSupportedPreviewSizes().get(0).height;
            if (parameters.isZoomSupported()) {
                Log.d(TAG, "initCamera: parameters.getMaxZoom = " + parameters.getMaxZoom());
            }

            camera.setDisplayOrientation(90);
            camera.setParameters(parameters);

        } catch (Throwable throwable) {
            parameters.unflatten(flatten);
            camera.setParameters(parameters);
        }
    }

    private void reInitCamera() {
        if (camera != null) {
            stopPreview();
            initCamera();
            startPreview();
        }
    }

    private void startPreview() {
        Log.d(TAG, "startPreview: ");
        camera.startPreview();
        autoFocus = new AutoFocus();
        autoFocus.start();
    }

    private void stopPreview() {
        camera.stopPreview();
        autoFocus.stop();
        autoFocus = null;

    }

    public void closeCamera() {
        stopPreview();
        //关闭闪关灯
        camera.release();
        camera = null;
    }

    //得到最好的预览大小
    private void findBsetPreviewSize(List<Camera.Size> supportedPreviewSizes) {

        int diff = Integer.MAX_VALUE;

        for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
            Log.d(TAG, "findBsetPreviewSize: supportedPreviewSize  height = " + supportedPreviewSize.height + "  width = " + supportedPreviewSize.width);
        }
    }



    private void openFlashLight() {
        if (camera != null) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            camera.setParameters(parameters);
        }
    }

    private void closeFlashLight() {
        if (camera != null) {
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
            camera.setParameters(parameters);
        }
    }

    private boolean isFlashLightON() {
        return parameters.getFlashMode() == Camera.Parameters.FLASH_MODE_TORCH;
    }


    class AutoFocus implements Camera.AutoFocusCallback {

        private Thread worker;
        private Handler workerHandler;
        private final int START_AUTO_FOCUS = 1001;
        private final int STOP_AUTO_FOCUS = 1002;
        private final long AUTO_FOCUS_INTERVAL_MS = 1000L;
        private CountDownLatch latch = new CountDownLatch(1);


        public AutoFocus() {
            new Thread() {
                @Override
                public void run() {
                    Looper.prepare();
                    workerHandler = new Handler(Looper.myLooper()) {
                        @Override
                        public void handleMessage(@NonNull Message msg) {
                            switch (msg.what) {
                                case START_AUTO_FOCUS: {
                                    camera.autoFocus(AutoFocus.this);
                                }
                                break;
                                case STOP_AUTO_FOCUS: {
                                    if (hasMessages(START_AUTO_FOCUS)) {
                                        removeMessages(START_AUTO_FOCUS);
                                    }
                                    camera.cancelAutoFocus();

                                    getLooper().quitSafely();
                                }
                            }
                        }
                    };
                    latch.countDown();
                    Looper.loop();
                }
            }.start();
        }

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            workerHandler.sendEmptyMessageAtTime(START_AUTO_FOCUS, AUTO_FOCUS_INTERVAL_MS);
        }

        public void start() {
            initLatch();
            workerHandler.sendEmptyMessage(START_AUTO_FOCUS);
        }

        public void stop() {
            initLatch();
            workerHandler.sendEmptyMessage(STOP_AUTO_FOCUS);
        }

        /**
         * 对线程调用start后线程并不一定立马执行，在这里需要确保其开始执行。
         * 使用这种方式会阻塞调用方，阻塞时长为系统调度线程开始执行的时间开销。
         * 这里还可以考虑另一种方式：使用handlerThread，调用方不断调用接口，当线程执行后才返回true，否则返回false。
         * 这样可以保证最小程度的阻塞，但是这对于调用方太复杂了，这里的阻塞是因为获取 looper 时，框架的实现会导致调用方阻塞：
         * 即线程运行后，looper 初始化完成前
         */
        private void initLatch() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
